﻿//////////////////////////////////////////////////////////////////
//
// City.cs
//
// Copyright (c) 2012 Dan Pike. All rights reserved
//
// Refer to license.txt for any license restrictions.
// 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Entities
{
   public class City : Entity
   {
      //////////////////////////////////////////////////////////////////////////
      //
      public City(World world, TradeAccessTypes access)
         : base(world)
      {
         factories = new SortedDictionary<Factory, int>(new ProductionSorter());
         productionDemand_ = new Dictionary<Material, double>();
         world.almanac.materials.ForEach(material => productionDemand_.Add(material, 0.0));

         base.count = 1.0;   // There's only one city of each type!
         access_ = access;
      }

      ////////////////////////////////////////////////////////////////////////
      //
      public void clear()
      {
         try
         {
            population_ = 0;
            factories.Keys.ToList().ForEach(factory =>
               {
                  factories[factory] = 0;
                  utilization_[factory] = 100;
               });
            productionDemand_.Keys.ToList().ForEach(demand => productionDemand_[demand] = 0);
         }
         catch (System.Exception ex)
         {
            logger_.exception(ex);
         }
      }

      ////////////////////////////////////////////////////////////////////////
      //
      public override double count
      {
         get { return base.count; }
         set { throw new InvalidOperationException("count property is read-only for City object"); }
      }

      ////////////////////////////////////////////////////////////////////////
      //
      public override bool dirty
      {
         get { return base.dirty; }
         set
         {
            // The city details have changed, though maybe not the nested objects
            base.dirty = value;
         }
      }

      ////////////////////////////////////////////////////////////////////////
      //
      internal bool load(string filename, Archive.City archive)
      {
         try
         {
            population_ = archive.population;
            if (!archive.factories.OfType<Archive.Factory>().All(archiveFactory =>
               {
                  Factory factory = world.almanac.entities[archiveFactory.name] as Factory;
                  if (null == factory)
                  {
                     logger_.errorFormat("do not recognize factory name '{0}' in city '{1}' in config file '{2}'",
                        archiveFactory.name, name, filename);
                     return false;
                  }
                  factories[factory] = archiveFactory.count;
                  setUtilization(factory, archiveFactory.utilization);
                  return true;
               }))
            {
               return false;
            }
            return true;
         }
         catch (System.Exception ex)
         {
            logger_.exception("load('{0}') - from archive '{1}'", ex, name, filename);
         }
         return false;
      }

      ////////////////////////////////////////////////////////////////////////
      //
      internal bool saveAs(string filename, Archive.WorldArchive archive)
      {
         try
         {
            Archive.City archiveCity = archive.cities[name];
            archiveCity.name = name;
            archiveCity.population = population_;
            factories.ToList().ForEach(kvp =>
               {
                  Archive.Factory archiveFactory = archiveCity.factories[kvp.Key.name];
                  archiveFactory.count = kvp.Value;
                  archiveFactory.utilization = utilization_[kvp.Key];
               });
            return true;
         }
         catch (System.Exception ex)
         {
            logger_.exception("saveAs('{0}') - to archive '{1}'", ex, name, filename);
         }
         return false;
      }

      //////////////////////////////////////////////////////////////////////////
      //
      public double getPopulationDemand(Material material)
      {
         try
         {
            // The demand caused by the population
            return material.perPopulation * material.growthRatio * population_;
         }
         catch (System.Exception ex)
         {
            logger_.exception(ex);
         }
         return 0.0;
      }

      //////////////////////////////////////////////////////////////////////////
      //
      public double getProductionDemand(Material material)
      {
         double demand = 0.0;
         try
         {
            // The demand caused by the industry (if that factory exists in the city)
            productionDemand_.TryGetValue(material, out demand);
         }
         catch (System.Exception ex)
         {
            logger_.exception(ex);
         }
         return demand;
      }

      //////////////////////////////////////////////////////////////////////////
      //
      public int population
      {
         get { return population_; }
         set
         {
            population_ = value;
            dirty = true;
         }
      }

      //////////////////////////////////////////////////////////////////////////
      //
      public override string ToString()
      {
         return name;
      }

      //////////////////////////////////////////////////////////////////////////
      //
      public bool getInProductionChain(Material material)
      {
         try
         {
            return factories.Keys.Any(factory => factory.getInProductionChain(material));
         }
         catch (System.Exception ex)
         {
            logger_.exception(ex);
         }
         return false;
      }

      //////////////////////////////////////////////////////////////////////////
      //
      private bool addProduction(Factory factory, double count)
      {
         try
         {
            // Add the demand for each of the inputs to this factory
            factory.inputs.All(kvp =>
               {
                  Material material = kvp.Key;
                  double ourDemand = count * kvp.Value;
                  productionDemand_[material] += ourDemand;

                  // DONT DO THIS AS THE REST OF THE CODE WILL THINK THAT THE CITY NEEDS
                  // THE DERIVED MATERIAL!
                  // If this is a derived product (e.g. we are a Tailor that
                  // needs cloth but that then will create a need for more wool),
                  // recurse to the producer (Weaver) and add in the demand for its
                  // inputs (wool)
                  //return (null == material.producer)   // true if this input is a raw material (no factory to make it)
                  //   || addProduction(material.producer, ourDemand / material.count);  // recurse to add the factories required to produce our input demand
                  return true;
               });

            // We return true to carry on with the recalculation (it's a Linq .All<>() loop)
            return true;
         }
         catch (System.Exception ex)
         {
            logger_.exception(ex);
         }
         return false;
      }

      //////////////////////////////////////////////////////////////////////////
      //
      public bool updateFactoryCount(Factory factory, int count)
      {
         bool changed = false;
         try
         {
            // Update the number of factories that we have
            int existingCount = 0;
            if (factories.TryGetValue(factory, out existingCount) && (count != existingCount))
            {
               factories[factory] = count;
               changed = true;
               dirty = true;
            }
         }
         catch (System.Exception ex)
         {
            logger_.exception(ex);
         }
         return changed;
      }

      //////////////////////////////////////////////////////////////////////////
      //
      public bool updateUtilization(Factory factory, int percent)
      {
         bool changed = false;
         try
         {
            // Update the utilization for our factories
            int existingPercent = 0;
            if (factories.TryGetValue(factory, out existingPercent) && (count != existingPercent))
            {
               utilization_[factory] = percent;
               changed = true;
               dirty = true;
            }
         }
         catch (System.Exception ex)
         {
            logger_.exception(ex);
         }
         return changed;
      }

      ////////////////////////////////////////////////////////////////////////
      //
      public double resupplyTime
      {
         get
         {
#warning "¬¬¬ Calculate resupply time"
            return 0.0;
         }
      }

      ////////////////////////////////////////////////////////////////////////
      //
      public double getProduction(Material material)
      {
         try
         {
            int factoryCount = 0;
            if ((null != material.producer) && factories.TryGetValue(material.producer, out factoryCount))
            {
               return factoryCount * material.count;
            }
         }
         catch (System.Exception ex)
         {
            logger_.exception(ex);
         }
         return 0.0;
      }

      ////////////////////////////////////////////////////////////////////////
      //
      public double getWarehouse(Material material)
      {
         try
         {
            // Warehousing required is the maximum of:
            // (1) The amount produced in the time it takes for the supply ship
            //     to return and take it away
            // (2) The amount required to supply the city in the same time
            return resupplyTime * Math.Max(getProduction(material),
               getProductionDemand(material) + getPopulationDemand(material));
         }
         catch (System.Exception ex)
         {
            logger_.exception(ex);
         }
         return 0.0;
      }

      //////////////////////////////////////////////////////////////////////////
      //
      public int getUtilization(Factory factory)
      {
         int percent = -1;
         if (!utilization_.TryGetValue(factory, out percent))
         {
            percent = -1;
         }
         return percent;
      }

      //////////////////////////////////////////////////////////////////////////
      //
      public void setUtilization(Factory factory, int percent)
      {
         try
         {
            utilization_[factory] = percent;
         }
         catch (System.Exception ex)
         {
            logger_.exception("{0}.setUtilization({1}, {2})", ex, name, factory.name, percent);
         }
      }

      //////////////////////////////////////////////////////////////////////////
      //
      public void addConvoy(Convoy convoy)
      {
         try
         {
            convoys_.Add(convoy);
         }
         catch (System.Exception ex)
         {
            logger_.exception(ex);
         }
      }

      //////////////////////////////////////////////////////////////////////////
      //
      public void removeConvoy(Convoy convoy)
      {
         try
         {
            convoys_.Remove(convoy);
         }
         catch (System.Exception ex)
         {
            logger_.exception(ex);
         }
      }

      //////////////////////////////////////////////////////////////////////////
      //
      public bool hasConvoy
      {
         get { return 0 != convoys_.Count; }
      }

      //////////////////////////////////////////////////////////////////////////
      //
      public bool getConvoysMatch(City otherCity)
      {
         try
         {
            if ((null != otherCity) && (convoys_.Count == otherCity.convoys_.Count))
            {
               return 0 == convoys_.Except(otherCity.convoys_).Count();
            }
         }
         catch (System.Exception ex)
         {
            logger_.exception(ex);
         }
         return false;
      }

      //////////////////////////////////////////////////////////////////////////
      //
      public List<Convoy> convoys
      {
         get { return convoys_.ToList(); }
      }

      //////////////////////////////////////////////////////////////////////////
      //
      public bool frozen
      {
         get { return frozen_; }
         set { frozen_ = value; }
      }

      //////////////////////////////////////////////////////////////////////////
      //
      public void recalculate()
      {
         try
         {
            productionDemand_.Keys.ToList().ForEach(material => productionDemand_[material] = 0.0);
            factories.All(kvp => addProduction(kvp.Key, kvp.Value));
         }
         catch (System.Exception ex)
         {
            logger_.exception(ex);
         }
      }

      //////////////////////////////////////////////////////////////////////////
      // The City's industry is a collection of factories
      // sorted into a production-sequence. Raw materials come after derived
      // products so that we can calculate the total demand more easily
      // by iterating the collection and building totals as we go
      class ProductionSorter : IComparer<Factory>
      {
         public ProductionSorter() { }
         public int Compare(Factory x, Factory y)
         {
            return x.precedence - y.precedence;
         }
      }
      public SortedDictionary<Factory, int> factories;

      //////////////////////////////////////////////////////////////////////////
      //
      private int population_;
      private bool frozen_ = true;
      private Dictionary<Material, double> productionDemand_;
      private Dictionary<Factory, int> utilization_ = new Dictionary<Factory, int>();
      private HashSet<Convoy> convoys_ = new HashSet<Convoy>();
      private TradeAccessTypes access_;
   }
}
